// -----------------------------------------------------------------------------
// | Library Includes
// -----------------------------------------------------------------------------
#include <sstream>






// -----------------------------------------------------------------------------
// | Crows3D Includes
// -----------------------------------------------------------------------------
#include "..\Crows3D\Crows3D.h"




#include <ezlogger_headers.hpp>



// ----------------------------------------------------------------------------- 
// | Function Prototypes
// -----------------------------------------------------------------------------
bool InitializeGraphics(HINSTANCE hInstance, int nCmdShow);
bool UpdateGraphics(void);
   bool updateFPS(void);
   bool updateText(void);
   bool updateBackground(void);
   bool updateShuttle(void);
bool RenderGraphics(void);
   bool renderBackground(void);
   bool render3D(void);
   bool render2D(void);
void CleanupGraphics(void);


// -----------------------------------------------------------------------------
// | Crows3D Classes
// -----------------------------------------------------------------------------

// Represents the 3D hardware interface
Crows3D::Device device;

// The target for our rendering
Crows3D::RenderTarget window; 

// Holds our graphics parameters
Crows3D::GraphicsSetupParameters param;



// For FPS calc and frame timing
Crows3D::FrameCounter fps;



// Pointers to models representing our skybox and space shuttle
Crows3D::Model *skybox;
Crows3D::Model *shuttle;



// Handles rendering text
Crows3D::TextRenderer text;

// The shaders and texture for this particular font object
Crows3D::EffectPosition2DTexture fontEffect;
Crows3D::Texture fontTexture;

// Handles for individual line of text
int fpsIndex;
int frameTime;



// Cameras for 2D and 3D rendering
Crows3D::Camera cam3D;
Crows3D::Camera cam2D;





// Used for FPS and frame timing metrics
int           timeDelta;
int           curFPS;
unsigned long minTime, maxTime;



// ----------------------------------------------------------------------------- 
// | WinMain - application entry point
// -----------------------------------------------------------------------------
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                    LPWSTR lpCmdLine, int nCmdShow)
{
   MSG msg = {0}; // Store received messages from windows
   
   EZLOGGERSTREAM << std::endl << std::endl << std::endl << std::endl;
   EZLOGGERSTREAM << "Entered WinMain!!" << std::endl;


   
   if (!InitializeGraphics(hInstance, nCmdShow))
   {
      EZLOGGERSTREAM << "ERROR: Failed to intiialize graphics" << std::endl;
      return 1; // Graphics init failure;
   }  


   
   bool EStop = false;// This will cause the loop to stop at the end of the current iteration
   
   // Main Game Loop
   while(!EStop)
   {
      // Used for profiling
      int winTime;
      int updateTime;
      int renderTime;
      
      // Handle input
      fps.StartHighPerformanceTimer();
      while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) == TRUE)
      {
         if ((msg.message == WM_KEYUP) && (msg.wParam == VK_ESCAPE))
         {
            PostQuitMessage(0);
         }
         else if (msg.message == WM_QUIT)
         {
            EStop = true;
         }

         TranslateMessage(&msg);
         DispatchMessage(&msg);
      }
      winTime = fps.StopHighPerformanceTimer();

      // Update
      fps.StartHighPerformanceTimer();
      {
         if (!UpdateGraphics())
         {
            // sometghing went wrong - Just try to clean up and quit for now
            break;
         }
      }
      updateTime = fps.StopHighPerformanceTimer();

      // Render
      fps.StartHighPerformanceTimer();
      {
         if (!RenderGraphics())
         {
            // sometghing went wrong - Just try to clean up and quit for now
            break;
         }
      }
      renderTime = fps.StopHighPerformanceTimer();

      // good place to drop a breakpoint at end of loop with negligible performance hit
      __asm
      {
         nop
      }
   }

   CleanupGraphics();

   return (int)msg.wParam;
}









bool InitializeGraphics(HINSTANCE hInstance, int nCmdShow)
{  
   Crows3D::ConfigParser config; // Loads config settings from files
   Crows3D::ModelLoader ml; // Handles loading models from *.obj files


   EZLOGGERSTREAM << "Initializing Graphics" << std::endl;
   


   // Load the graphics configuration settings from Config.txt
   if (!config.LoadGraphicsSetupParameters("Config.txt", param))
   {
      EZLOGGERSTREAM << "ERROR: Couldn't get configuration info from config.txt" << std::endl;
      return false;
   }



   
   // Create our window with these dimensions
   if(!window.CreateRenderTarget(hInstance, nCmdShow, param.width, 
                                 param.height, param.windowed))
   {
      EZLOGGERSTREAM << "ERROR: Couldn't create window with specified parameters" << std::endl;
      return false;
   }


   

   // Set up the Direct3D device!
   if (!device.Initialize(window.GetHwnd(), param))
   {
      EZLOGGERSTREAM << "ERROR: Couldn't initialize Direct3D" << std::endl;
      return false;
   }


   

   // Initialize the fps class, this does fps calculation and high perf timing
   if (!fps.Initialize())
   {
      EZLOGGERSTREAM << "ERROR: Couldn't create FPS tracking class" << std::endl;
      return false;
   }


   EZLOGGERSTREAM << "Allocating models for the skybox and space shuttle" << std::endl;

   skybox = new Crows3D::Model();
   shuttle = new Crows3D::Model();

   
   EZLOGGERSTREAM << "Loading the skybox model from Resources\\Models\\cube.obj" << std::endl;

   if (!ml.LoadObjModel(&device, ".\\Resources\\Models\\cube.obj", skybox))
   {
      EZLOGGERSTREAM << "ERROR: Failed to load cube model from file" << std::endl;
      return false;
   }

   EZLOGGERSTREAM << "Loading the skybox model from Resources\\Models\\cube.obj" << std::endl;

   if (!ml.LoadObjModel(&device, ".\\Resources\\Models\\Shuttle2.obj", 
                        shuttle))
   {
      EZLOGGERSTREAM << "ERROR: Failed to load cube model from file" << std::endl;
      return false;
   }


   EZLOGGERSTREAM << "Loading Pixel and Vertex shaders for font rendering" << std::endl;

   std::wstring vs(L".\\Resources\\Shaders\\Position2DTextures.vs");
   std::wstring ps(L".\\Resources\\Shaders\\Position2DTextures.ps");

   if (!fontEffect.Initialize(&device, vs, ps))
   {
      EZLOGGERSTREAM << "ERROR: Failed to create font effect" << std::endl;
      return false;
   }


   EZLOGGERSTREAM << "Loading texture for font rendering" << std::endl;

   std::wstring sTex(L".\\Resources\\Fonts\\font.dds");

   if (!fontTexture.Initialize(&device, sTex))
   {
      EZLOGGERSTREAM << "ERROR: Failed to create font texture" << std::endl;
      return false;
   }

   EZLOGGERSTREAM << "Loading data to parse font texture" << std::endl;

   if (!text.Initialize(&device, ".\\Resources\\Fonts\\fontdata.txt", 
                        &fontTexture, &fontEffect))
   {
      EZLOGGERSTREAM << "ERROR: Failed to load font parsing data" << std::endl;
      return false;
   }

   // Set up text
   fpsIndex  = text.CreateLine(&device);
   frameTime = text.CreateLine(&device);

   if (!text.UpdateLine(&device, fpsIndex, "FPS: ", 0.0f, 0.0f, 
                        (float)param.width, (float)param.height))
   {
      EZLOGGERSTREAM << "ERROR: Failed to update line" << std::endl;
      return false;
   }

   if (!text.UpdateLine(&device, frameTime, "Frame Timing: ", 0.0f, 20.0f, 
                        (float)param.width, (float)param.height))
   {
      EZLOGGERSTREAM << "ERROR: Failed to update line" << std::endl;
      return false;
   }



   skybox->SetPosition(0.0f, 0.0f, 0.0f);
   skybox->SetRotation(0.0f, 0.0f, 0.0f);
   skybox->SetScaling(100.0f, 100.0f, 100.0f);

   shuttle->SetPosition(0.0f, 0.0f, 0.0f);
   shuttle->SetRotation(0.0f, 0.0f, 0.0f);
   shuttle->SetScaling(0.005f, 0.005f, 0.005f);


   // Setup our camera
   cam3D.SetupPerspective((float)D3DX_PI / 4.0f, (float)param.width / param.height, 0.1f, 1000.0f);
   cam3D.SetPosition(0.0f, 0.0f, 0.0f);
   cam3D.SetYawPitchRoll(0.0f, -0.2f, 0.0f); 

   cam2D.SetupOrthographic((float)param.width, (float)param.height, 0.1f, 1000.0f);
   cam2D.SetPosition(0.0f, 0.0f, -1.0f);

   return true;
}






bool UpdateGraphics(void)
{
   // Calculate our FPS, track min/max
   if (!updateFPS())
   {
      return false;
   }
      
   // Update the text output  
   if (!updateText())
   {
      return false;
   }

   // Rotate our background by PI/4 rad/s
   if (!updateBackground())
   {
      return false;
   }

   // Rotate our shuttle by PI/4 rad/s
   if (!updateShuttle())
   {
      return false;
   }

   return true;
}


bool updateFPS(void)
{
   timeDelta = fps.Frame();
   curFPS    = fps.GetCurrentFPS();

   minTime   = fps.GetCurrentMinTime();
   maxTime   = fps.GetCurrentMaxTime();

   return true;
}
bool updateText(void)
{
   std::stringstream ss;

   ss << "FPS: " << curFPS;


   if (!text.UpdateLine(&device, fpsIndex, ss.str(), 0.0f, 0.0f, (float)param.width, (float)param.height))
   {
      return false;
   }
   
   ss.str(std::string());
   ss.precision(4);
   ss << "Frame timing: [avg = " << (1000000.0f / curFPS) << " us]";
   ss << "[min = " << minTime << " us]";
   ss << "[max = " << maxTime << " us]";

   if (!text.UpdateLine(&device, frameTime, ss.str(), 0.0f, 20.0f, (float)param.width, (float)param.height))
   {
      return false;
   }

   return true;
}

bool updateBackground(void)
{
   static float angleBackground = 0.0f;

   angleBackground += (float)((-3.1415927 * ((double)timeDelta / 1000000.0)) / 16.0);

   if (angleBackground > 2.0f * 3.1415927)
   {
      angleBackground -= 2.0f * 3.1415927f;
   }

   skybox->SetRotation(0.0f, angleBackground, 0.0f);

   return true;
}

bool updateShuttle(void)
{
   static float angleShuttle = 0.0f;

   angleShuttle += (float)((3.1415927 * ((double)timeDelta / 1000000.0)) / 4.0);

   if (angleShuttle > 2.0f * 3.1415927)
   {
      angleShuttle -= 2.0f * 3.1415927f;
   }

   shuttle->SetRotation(0.0f, angleShuttle, 0.0f);

   return true;
}




bool RenderGraphics(void)
{
   // Set up our backcolor
   float backcolor[] = {0.1f, 0.5f, 0.5f, 1.0f};
   
   // Clear the backbuffer/z-buffer
   device.ClearBackbuffer(backcolor);
   device.ClearDepthStencil(1.0f, 0);

   // Render background stuff
   if (!renderBackground())
   {
      return false;
   }

   // Render 3D stuff
   if (!render3D())
   {
      return false;
   }

   // Render text and HUD last
   if (!render2D())
   {
      return false;
   }

   // Flip the backbuffer to the screen
   device.Swap();


   return true;
}
bool renderBackground(void)
{
   // Disable the z-buffer so this doesn't occlude any foreghround objects
   // No alpha to deal with...
   device.EnableDepthBuffer(false);
   device.EnableAlphaBlending(true);

   // Set up our 3D (perspective) camera
   cam3D.SetPosition(0.0f, 0.0f, 0.0f);

   skybox->Render(&device, &cam3D);

   return true;
}
bool render3D(void)
{
   // Re-enable the z-buffer
   // No alpha to deal with...
   device.EnableAlphaBlending(true);
   device.EnableDepthBuffer(true);


   // Set up our 3D (perspective) camera
   cam3D.SetPosition(0.0f, 2.0f, 10.0f);

   // Render the shuttle!
   shuttle->SetPosition(0.0f, 0.0f, 0.0f);
   shuttle->Render(&device, &cam3D);

   return true;
}
bool render2D(void)
{
   // disable z-buffer so this will overdraw everything
   // Enable alpha so we mask out text backgrounds
   device.EnableDepthBuffer(false);
   device.EnableAlphaBlending(true);


   // Set up our 2D (ortho) camera
   cam2D.SetPosition(0.0f, 0.0f, -1.0f);
   cam2D.SetYawPitchRoll(3.1415927f, 0.0f, 0.0f);

   // Render the text!
   text.RenderLines(&device, &cam2D);

   return true;
}


void CleanupGraphics(void)
{
   delete shuttle;
   delete skybox;
}






